#!/usr/bin/perl -T

# (C) 2007 The Trustees of Indiana University.  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
#

use strict;
use 5.004;

#use CGI qw/:standard/;
use CGI qw(header);

use DBI;
use DBI qw(:sql_types);
use DBD::mysql;
use Socket;
use Digest::MD5 qw(md5_hex);
use List::Util qw[min max];
use File::Temp qw/ tempfile tempdir /;
use XML::Simple qw/XMLin /;
use Data::Dumper;
use Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex);
use Digest::SHA1  qw(sha1_hex);
use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex);
#use Switch;

#use a global for the dbhandler..
my $dbh;



#----------------------------------
#-----now the functions

#small print for helps
sub internal_error{
    print header();
    print "Internal Error";
}

#-- prints an xml formatted error 
sub xml_report_error{
    my $error_msg=shift;
    print "<error>".$error_msg."</error>";
    
}

#--takes an hex string and adds left side zeros until
#  it is of at least 16 bytes in length
sub fix_user_id_zeros{
   my $user_id=shift;
   while((length $user_id)<16){
       $user_id="0".$user_id;  
   } 
   return $user_id;
}


#Helper: ransforms a hex string into a quad hex_string
#does not do chatacter validation
sub hex_into_quad_hex{
   my $in_val=shift;
   my $new_val="";
   my $i;

   for ($i=0;$i<=length($in_val)-4;$i=$i+4){
      $new_val=substr($in_val,length($in_val)-4-$i,4).":".$new_val;
   }
   #do last one..
   if(0!= length($in_val)%4 ){
      $new_val=substr($in_val,0,length($in_val)%4).":".$new_val;
   }
   #remove last one 
   $new_val=substr($new_val,0,length($new_val)-1);
   #print "old=$in_val new=$new_val";
   #return $new_val;
}

sub update_ratings{
    my $user_id=shift;
    my $auth_info=shift;
    my $filename=shift;

    my $auth_query= "SELECT auth_info from user where user_id=0x".$user_id;
    my $auth_sth = $dbh->prepare($auth_query);


    $auth_sth->execute() or die "cannot execute query $auth_query";
    my @row;
    my $user_auth;

    if(@row= $auth_sth->fetchrow_array()){
      #print "<p>".$row[0]."  ".$row[1]." ".$row[2]." ".$row[3] ."</p>";
      #print "<user    id=\"".hex_into_quad_hex(fix_user_id_zeros($row[0]))."\" last_seen=\"".$row[1]."\" ";
       $user_auth=$row[0]; 
       open( FILE, "< $filename" ) or die "Can't open $filename : $!";
       #my $hmac = Digest::HMAC_MD5->new($user_auth);
       my $hmac = Digest::HMAC_SHA1->new($user_auth);
       #print "auth = $user_auth ";
       $hmac->addfile(*FILE);
       my $digest = $hmac->hexdigest;
       if($auth_info ne $digest){
          print "Error, Cannot authenticate your information \n";
          return;
       }

       print  "digest1= $digest \n";

        use Digest::MD5;
        use Digest::SHA1;
       #my $ctx = Digest::MD5->new;
       my $ctx = Digest::SHA1->new;
       #$ctx->add($data);
       open( FILE2, "< $filename" ) or die "Can't open $filename : $!";
       $ctx->addfile(*FILE2); 
       my $digest2 = $ctx->hexdigest;
       print  "digest2= $digest2 \n";

       #print ">";
       #print " </user>\n";
    }
    #next lines for timing tests
    #print "End of verification\n";
    #return;




    # create object
    my $xml = new XML::Simple;

    # read XML file
    my $data = $xml->XMLin($filename, ForceArray => 1,keyattr => []);
    #my $data = $xml->XMLin("/tmp/8Eot305frb");

    #next lines for timing tests
    #print "End of XML transformation\n";
    #return;
 


    # print output
    #print Dumper($data);
    #now iterate (need to add a first iteration for input validation)
    if(not (defined $data->{id}) || not (defined $data->{ver})){
       xml_report_error("Update file with no version or id");
       return ;
    }
    if(not ($data->{id} eq $user_id)){
       xml_report_error("Reported user id and file user id do not match file_id=".
                         $data->{id}." input_id= $user_id ");
       return;
      
    } 
    #print "user_id= $user_id file_id=".$data->{id}. "version=".$data->{ver}."\n";
    foreach my $loc (@{$data->{loc}}){
       #print $loc->{id}."\n";
       if(!  ( $loc->{id} =~/^([^'; \t\n\r\f]{1,256})$/  )  ){
          xml_report_error("The site id looks invalid, aborting\n");
          return;
       }
       

       if(defined $loc->{xprate} && defined $loc->{visit} ){
          xml_report_error("site has both implicit and explicit ratings");
          return;
       }       

       if(defined $loc->{xprate}){ 
           #print "xprate=".((@{$loc->{xprate}})[0])->{val}."\n";
           if( !(((@{$loc->{xprate}})[0])->{val} =~/^([ 0-9-]{1,3})$/) ){
             xml_report_error("explicit rating '".((@{$loc->{xprate}})[0])->{val}."'is not valid for ".$loc->{id});
             return;
           }
       }
       if (defined $loc->{visit}){
           #print "visit ";
           #print "count=".((@{$loc->{visit}})[0])->{count};
           #print "init=".((@{$loc->{visit}})[0])->{init};
           #print "last=".((@{$loc->{visit}})[0])->{last};

           if( !(((@{$loc->{visit}})[0])->{count} =~/^(\d{1,2})$/) ){
             xml_report_error("Count value is not valid for ".$loc->{id});
             return;
           }
           if( !(((@{$loc->{visit}})[0])->{init} =~/^(\d{1,20})$/) ){
             xml_report_error("Init value is not valid for ".$loc->{id});
             return;
           }
           if( !(((@{$loc->{visit}})[0])->{last} =~/^(\d{1,20})$/) ){
             xml_report_error("Last value is not valid for ".$loc->{id});
             return;
           }


           if(defined $loc->{comment}  ){
              #print "comment='".((@{$loc->{comment}})[0])."'";
              if (length ( (@{$loc->{comment}})[0]   ) >256 ){
                    xml_report_error("Comment is too long for ".$loc->{id});
                    return;
              } 
           }
       }
    }
    #now we iterate again for insertion into DB...
    my $loc_query="INSERT IGNORE INTO url (url) values ( ? )";
    my $loc_sth=$dbh->prepare($loc_query);
    my $loc_query2 = "SELECT url_id from url where url= ?";
    my $loc_sth2 = $dbh->prepare($loc_query2);
    ##  maybe having a replace delayed would be more appropiate?  
    my $rating_ins_query  ="INSERT DELAYED INTO peer_rating ";
       $rating_ins_query .=" (url_id, user_id,explicit_rating,implicit_rating,timestamp,first)";
       $rating_ins_query .= " values (? , 0x".$user_id." , ? ,?,?,?) ON DUPLICATE KEY UPDATE ";
       $rating_ins_query .= " explicit_rating= ? , implicit_rating= ?, timestamp= ?, first= ?";
    my $rating_sth=$dbh->prepare($rating_ins_query) or die 'cannot prepare rating statement';

    my $comment_query  = "REPLACE INTO peer_coments (url_id,user_id,comment) VALUES";
       $comment_query .= " (?,0x".$user_id.",unhex(?))";
    my $comment_sth=$dbh->prepare($comment_query);


    my $url_id;
    my @row;
    my $now=time(); ##needed for intermediate fix!!!
    my $xplicit;
    my $implicit;
    my $first;
    my $last;
    my $loc_comment;

    foreach my $loc (@{$data->{loc}}){
       #print $loc->{id}."\n";
       $loc_sth->execute($loc->{id}) or die "Cannot insert url";
       $loc_sth2->execute($loc->{id}) or die "Cannot query for url_id";
       
       if(@row= $loc_sth2->fetchrow_array()){
          $url_id=$row[0];
       }
       else{
         die "did not find url_id";
       }

       if(defined $loc->{comment}  ){
              $loc_comment= (@{$loc->{comment}})[0];
              #print "comment='".((@{$loc->{comment}})[0])."'";
              #print "loc_Comment= $loc_comment pk=". unpack("H*",$loc_comment);
              
              #$comment_sth->execute($url_id,((@{$loc->{comment}})[0])) or
              #    die 'cannot insert comment';

              $comment_sth->execute($url_id,unpack("H*",$loc_comment)) or
                  die 'cannot insert comment';


       }


       $xplicit=0;
       $implicit=0;
       $first=0;
       $last=0;

       if(defined $loc->{xprate}){
           $xplicit=((@{$loc->{xprate}})[0])->{val};
           #print "explicit=$xplicit";
       }
       if (defined $loc->{visit}){
           #print "visit ";
           $implicit=((@{$loc->{visit}})[0])->{count};
           $first=((@{$loc->{visit}})[0])->{init};
           $last=((@{$loc->{visit}})[0])->{last};
           
       }
       #do the update (one pass is bad, a better way would be a two step pass)
       $rating_sth->execute($url_id,$xplicit,$implicit,$last,$first,
                                         $xplicit,$implicit,$last,$first) or die 
         'cannot insert rating';


    }
    
    print "user_id = $user_id end?";

    #print_user_info($user_id);

}

#adds an user and prints the user info if possible
sub add_user{
   my $user_id=shift;
   my $auth_info=shift;
 
   my $salt=int(rand(2147483648));
  
   my $query ="insert ignore into user (user_id,last_seen,auth_info)";
      $query .=" values (0x". $user_id.",unix_timestamp(now()), '".$auth_info."')";
   
   my $sql = $dbh->prepare($query);

   $sql->execute() or die "cannot execute query $query";
   return print_user_info($user_id,$salt);

}

#prints the user info for a given user_id
sub print_user_info{
  my $user_id= shift;
  my $salt=shift;

  my $md5;
  my $to_hash;

  my $query="select lower(hex(user_id)),last_seen,auth_info  from user where user_id=0x".$user_id;


  my $sql = $dbh->prepare($query);

  $sql->execute() or die "cannot execute query $query";
  my @row;


  if(@row= $sql->fetchrow_array()){
      #print "<p>".$row[0]."  ".$row[1]." ".$row[2]." ".$row[3] ."</p>";
      print "<user    id=\"".hex_into_quad_hex(fix_user_id_zeros($row[0]))."\" last_seen=\"".$row[1]."\" ";
      if(0!=length($row[2]) && (defined $salt)){
         $to_hash=$salt.md5_hex($row[2]); 
         #print " auth_info=\"".$row[2]."\" salt=\"".$salt."\" pre=\" $to_hash \"  total=".md5_hex($to_hash) ;
         print " salt=\"".$salt."\"  hash=\"".md5_hex($to_hash)."\"";
      }
       
      print ">";
      print " </user>\n";
  }

}

sub get_random_active_user_id{
   my $query="select lower(hex(user_id)) from peer_rating group by user_id order by rand() limit 1";
   my @row;  

   my $sql = $dbh->prepare($query);

   $sql->execute() or die "cannot execute query $query";
   if(@row= $sql->fetchrow_array()){
     return $row[0];
   }  
   return 0;
   
}

sub print_user_rankings{
  my $user_id= shift;
  my $last_seen=time();

  my $query="select url,implicit_rating,timestamp,comment,first,explicit_rating from peer_rating left join peer_coments on peer_coments.user_id=peer_rating.user_id and peer_coments.url_id=peer_rating.url_id, url where url.url_id=peer_rating.url_id and peer_rating.user_id=0x".$user_id." order by url";

   
  my $sql = $dbh->prepare($query);

  $sql->execute() or die "cannot execute query $query";
  my @row;

  my $query2="select last seen from user where user_id=0x".$user_id;

  #add last..
  $user_id=fix_user_id_zeros($user_id);
  print "<nt-client-db id=\"".$user_id."\" ver=\"0.0.2\" last=\"".$last_seen."\"  > \n";
  #  print "<nt-client-db id=\"".hex_into_quad_hex($user_id)."\" ver=\"0.0.1\" last=\"".$last_seen."\"  > \n";

  while(@row= $sql->fetchrow_array()){
      #print "<p>".$row[0]."  ".$row[1]." ".$row[2]." ".$row[3] ."</p>";
      print " <loc id=\"".$row[0]."\" >\n";
      if(0==$row[5]){
         print "    <visit init=\"".$row[4]."\" count=\"".$row[1]."\" last=\"".$row[2]."\" />\n";
      }
      else{
         print "    <xprate val=\"".$row[5]."\" />\n";
      }
      if(0!=length($row[3])){
         print "    <comment>".$row[3]."</comment>\n";
      }

      #print "    <comment>".$row[3]."</comment>\n";
      print " </loc>\n";
  }
  print "</nt-client-db>"

  

#
#select url,implicit_rating,timestamp,comment from peer_rating left join peer_coments on peer_coments.user_id=peer_rating.user_id and peer_coments.url_id=peer_rating.url_id, url where url.url_id=peer_rating.url_id and peer_rating.user_id=65537;


}

sub main {
   my $cgi = new CGI;
   my $user_id;
   my %user_input;
   my $com;  
   my $auth_info;
   my $temp;
   #initialize connection to db
  $dbh = DBI->connect('DBI:mysql:NetTrust','nettrust','');
  if(not defined ($dbh)){
       internal_error();
       return;
  }


  #--- untaint variables
   if(defined($cgi->param('id')) && $cgi->param('id') =~/^([a-fA-F0-9:]{1,160})$/){
       $temp=$1;
       if($temp =~/^([a-fA-F0-9:]+)$/ ){
          $user_id=join("",split(/:/,$temp));
          #print header();
          #print "hello '$temp' $user_id ".hex($user_id);
          #hex_into_quad_hex($user_id);
          #return;
       }
       if( $temp =~ /^(\d+)$/){
          #$user_id = $1;
          $user_id=sprintf("%x", $temp);
       }
   }


   if(defined($cgi->param('com')) && $cgi->param('com') =~/^(\w{1,20})$/){
       $user_input{"command"} = $1 ;
       $com =$1;        
   }
   if(defined($cgi->param('auth_info')) && $cgi->param('auth_info') =~/^([a-fA-F0-9]{1,200})$/){

       $auth_info = $1;
   }


   print header(
              -TYPE => 'text/xml');

   if("ratings" eq $com){
      if (not (defined $user_id)){
        $user_id= get_random_active_user_id();
        print("You need a user_id\n");
      }
      else{
        print_user_rankings($user_id);
      }
   }
   else { 
      if ("info" eq $com) {
        if (defined $user_id){
          print_user_info($user_id);
        }
      }
      else{
        if( "add_user" eq $com){
          if(not (defined $auth_info) or not (defined $user_id)){
             print("Need both auth info and user_id to add a user\n");
             return;
          }
          add_user($user_id,$auth_info);
          
        }
        else{
          if ("update_ratings" eq $com){
            if(not (defined $user_id) or not (defined $auth_info)){ 
              print ("Need both user id and auth_info to to make an update");
              return;
            }
            #--- now we save the input as a temporary file...
            my $filename;
            my $fh ;
            ($fh, $filename)= tempfile();            
            my $upload_filehandle = $cgi->upload("photo");
            while ( <$upload_filehandle> )
            {
                print $fh $_;
            }
            close($fh);
            #--do minor file validation...


            #----call the handler..
            update_ratings($user_id,$auth_info,$filename);
            #print ("hello $filename");

            #finalize with cleanup...
            unlink($filename);

          }
          else{
            print ("unrecognized or invalid options com=$com id=$user_id\n");
          }
        }
      }
   }


   #print header();
   #print ("hello_print");

   
   #print_user_rankings($user_id);

   #-warn("hello\n");
   $dbh->disconnect;
}

main();

